
#include"yolo.h"
using namespace std;
using namespace cv;
using namespace dnn;

bool Yolo::readModel(Net &net, string &netPath,bool isCuda = false) {
	try {
		net = readNet(netPath);
	}
	catch (const std::exception&) {
		return false;
	}
	//cuda
	if (isCuda) {
		// net.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA);
		// net.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA_FP16);
		net.setPreferableBackend(cv::dnn::DNN_BACKEND_DEFAULT);
		net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU);

	}
	//cpu
	else {
		
		net.setPreferableBackend(cv::dnn::DNN_BACKEND_DEFAULT);
		net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU);
	}
	return true;
}
bool Yolo::Detect(Mat &SrcImg, Net &net, vector<Output> &output) {
	Mat blob;
	int col = SrcImg.cols;
	int row = SrcImg.rows;
	int maxLen = MAX(col, row);
	Mat netInputImg = SrcImg.clone();
	if (maxLen > 1.2*col || maxLen > 1.2*row) {
		Mat resizeImg = Mat::zeros(maxLen, maxLen, CV_8UC3);
		SrcImg.copyTo(resizeImg(Rect(0, 0, col, row)));
		netInputImg = resizeImg;
	}
	blobFromImage(netInputImg, blob, 1 / 255.0, cv::Size(netWidth, netHeight), cv::Scalar(104, 117,123), true, false);
	//如果在其他设置没有问题的情况下但是结果偏差很大，可以尝试下用下面两句语句
	//blobFromImage(netInputImg, blob, 1 / 255.0, cv::Size(netWidth, netHeight), cv::Scalar(0, 0,0), true, false);
	//blobFromImage(netInputImg, blob, 1 / 255.0, cv::Size(netWidth, netHeight), cv::Scalar(114, 114,114), true, false);
	net.setInput(blob);
	std::vector<cv::Mat> netOutputImg;
	//vector<string> outputLayerName{"345","403", "461","output" };
	//net.forward(netOutputImg, outputLayerName[3]); //获取output的输出
	net.forward(netOutputImg, net.getUnconnectedOutLayersNames());
	std::vector<int> classIds;//结果id数组
	std::vector<float> confidences;//结果每个id对应置信度数组
	std::vector<cv::Rect> boxes;//每个id矩形框
	float ratio_h = (float)netInputImg.rows / netHeight;
	float ratio_w = (float)netInputImg.cols / netWidth;
	int net_width = className.size() + 5;  //输出的网络宽度是类别数+5
	float* pdata = (float*)netOutputImg[0].data;
	for (int stride =0; stride < 3; stride++) {    //stride
		int grid_x = (int)(netWidth / netStride[stride]);
		int grid_y = (int)(netHeight / netStride[stride]);
		for (int anchor = 0; anchor < 3; anchor++) { //anchors
			const float anchor_w = netAnchors[stride][anchor * 2];
			const float anchor_h = netAnchors[stride][anchor * 2 + 1];
			for (int i = 0; i < grid_y; i++) {
				for (int j = 0; j < grid_x; j++) {
					float box_score = pdata[4]; //Sigmoid(pdata[4]);//获取每一行的box框中含有某个物体的概率
					if (box_score > boxThreshold) {
						cv::Mat scores(1, className.size(), CV_32FC1, pdata + 5);
						Point classIdPoint;
						double max_class_socre;
						minMaxLoc(scores, 0, &max_class_socre, 0, &classIdPoint);
						max_class_socre = (float)max_class_socre; //Sigmoid((float)max_class_socre);
						if (max_class_socre > classThreshold) {
							//rect [x,y,w,h]
							float x = pdata[0];// (Sigmoid(pdata[0]) * 2.f - 0.5f + j) * netStride[stride];  //x
							float y = pdata[1];// (Sigmoid(pdata[1]) * 2.f - 0.5f + i) * netStride[stride];   //y
							float w = pdata[2];// powf(Sigmoid(pdata[2]) * 2.f, 2.f) * anchor_w;   //w
							float h = pdata[3];// powf(Sigmoid(pdata[3]) * 2.f, 2.f) * anchor_h;  //h
							int left = (x - 0.5*w)*ratio_w;
							int top = (y - 0.5*h)*ratio_h;
							classIds.push_back(classIdPoint.x);
							confidences.push_back(max_class_socre*box_score);
							boxes.push_back(Rect(left, top, int(w*ratio_w), int(h*ratio_h)));
						}
					}
					pdata += net_width;//下一行
				}
			}
		}
	}

	//执行非最大抑制以消除具有较低置信度的冗余重叠框（NMS）
	vector<int> nms_result;
	NMSBoxes(boxes, confidences, classThreshold, nmsThreshold, nms_result);
	for (int i = 0; i < nms_result.size(); i++) {
		int idx = nms_result[i];
		Output result;
		result.id = classIds[idx];
		result.confidence = confidences[idx];
		result.box = boxes[idx];
		output.push_back(result);
	}

	if (output.size())
		return true;
	else
		return false;
}

// 显示结果
// result 结构体。里面含有检测框的相关信息  这里传进来的是排好序的框集合
void Yolo::drawPred(Mat &img, vector<Output> result, vector<Scalar> color) {
	vector<string> parking_plot_number; // 检测的一张图中所有的字符集合 
	string car_plot_number;  // 车位号
	
	
	cout<<"检测出字符串：";
	for (int i = 0; i < result.size(); i++) {
		int left, top;
		left = result[i].box.x;
		top = result[i].box.y;
		int color_num = i;

		// c与c++中画矩形的api
		// C: void cvRectangle(CvArr* img, CvPoint pt1, CvPoint pt2, CvScalar color, int thickness=1, int line_type=8, int shift=0 )
		// C++: void rectangle(Mat& img, Point pt1,Point pt2,const Scalar& color, int thickness=1, int lineType=8, int shift=0)
		// C++: void rectangle(Mat& img, Rect rec, const Scalar& color, int thickness=1, int lineType=8, int shift=0 )
		
		rectangle(img, result[i].box, color[result[i].id], 2, 8);

		// 类别或概率存在label里面
		// string label = className[result[i].id] +":" + to_string(result[i].confidence);
		string label = className[result[i].id];					 
		
		int baseLine;
		Size labelSize = getTextSize(label, FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);
		top = max(top, labelSize.height);
		//rectangle(frame, Point(left, top - int(1.5 * labelSize.height)), Point(left + int(1.5 * labelSize.width), top + baseLine), Scalar(0, 255, 0), FILLED);
		
		// 图片上显示label（类别与概率）
		putText(img, label, Point(left, top), FONT_HERSHEY_SIMPLEX, 1, color[result[i].id], 2);
		
		// 比较最外的大框标签，车位号不含大框，需要去除，compare函数比较字符串，保存除大框标签的字符集合（本代码定义的大框标签为"parking plot number"）
		if( className[result[i].id].compare("parking plot number") ){
			parking_plot_number.push_back(className[result[i].id]);
			// -----------输出检测的字符----------------
			cout<<className[result[i].id];
		}
	}
	cout<<endl;


	// -------------判段字符集合中的车位号并打印---------------------
	int flags = 0;
	int k = 2;
	// 去除字符少于3的图片（车位号至少4位）
	for(int i=0;i < parking_plot_number.size()-3;i++){
		if(parking_plot_number.size()<4){
			cout<<"无法检测到车位号，请重新输入图片！"<<endl;
			putText(img, "Recognition parking plot number is failed!", Point(5, 60), FONT_HERSHEY_SIMPLEX, 1, Scalar(0,255,0), 2);
			flags = 1;  // 
			break;
		}
		// cout<<"parking_plot_number:"<<parking_plot_number[i].compare("A")<<endl;

		// 寻找以字母+3个数字组成的车位号
		if( parking_plot_number[i][0] < 'A'
			|| parking_plot_number[i][0] > 'E'  )
		{
			continue;
		}
		else{
			if(	   parking_plot_number[i+1][0] >= 'A'
				&& parking_plot_number[i+1][0] <= 'E' )
					continue;
			else{
				if(    parking_plot_number[i+1][0] >= 'A'
					&& parking_plot_number[i+1][0] <= 'E')
						continue;
				else{
					if(    parking_plot_number[i+1][0] >= 'A'
						&& parking_plot_number[i+1][0] <= 'E')
							continue;
					else{
						// 拼接车位字符组成车位号
						car_plot_number = parking_plot_number[i].append(parking_plot_number[i+1]).append(parking_plot_number[i+2]).append(parking_plot_number[i+3]);
						cout<<car_plot_number<<"		";
						flags =1;
						
						// 图片上显示车位号，如果检测2张图片，车位号会重复，需要修改代码，
						putText(img, ("Parking plot number is : ")+car_plot_number, Point(5, k*30), FONT_HERSHEY_SIMPLEX, 1, Scalar(0,255,0), 2);
						k++;
					}
				}		
			}
		}	
	}

	if(flags == 0){
			cout<<"无法检测到车位号，请重新输入图片！"<<endl;
			putText(img, "Recognition parking plot number is failed!", Point(5, 60), FONT_HERSHEY_SIMPLEX, 1, Scalar(0,255,0), 2);
			flags = 0;
		}
	cout<<"\n------------------------\n";

}

// -----------按检测框横坐标排序------------
std::vector<Output> Yolo::Sort(std::vector<Output> result) {
	std::vector<Output> result_sort_x;
	vector<int>X;
	for (int i = 0; i < result.size(); i++) {
		// Rect（左上角x坐标 ， 左上角y坐标，矩形的宽，矩形的高）
		int left, top;
		int w,h;
		left = result[i].box.x;
		top = result[i].box.y;
		w = result[i].box.width;
		h = result[i].box.height;
		X.push_back(left);
	}
	sort(X.begin(), X.end());  // 按横坐标排序

	// 按横坐标排序的顺序，排检测框信息（result）的顺序
	for(vector<int>::iterator it=X.begin(); it!=X.end(); it++){
        // cout<<*it<<" ";
		for(int j = 0;j < result.size();++j){
			if(*it == result[j].box.x)
				result_sort_x.push_back(result[j]);
		}
    }
	return result_sort_x;   // 返回排好顺序的检测框集合
}
